package com.ease.context.ioc;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.access.BeanFactoryLocator;
import org.springframework.beans.factory.access.BeanFactoryReference;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
import org.springframework.util.WeakReferenceMonitor;

import com.ease.common.util.Assertion;
import com.ease.common.util.Detect;

/**
 * usage:
 * <p>
 * public class MockAbstractServiceBeanImpl extends AbstractServiceBean { MockAbstractServiceBeanImpl
 * mockAbstractServiceBean = new MockAbstractServiceBeanImpl();
 * assertNotNull(mockAbstractServiceBean.getMockBeanFromSpring()); }
 * 
 * @author nathanleewei
 * 
 */
public abstract class AbstractServiceBean extends Detect {

	private static final String CONFIG_LOCATION = "classpath*:applicationContextClientContext.xml";

	private static final String BEAN_FACTORY_LOCATOR_KEY = "beanFactoryLocatorKey";

	protected String beanFactoryLocatorKey = BEAN_FACTORY_LOCATOR_KEY;

	protected String configLocation = CONFIG_LOCATION;

	private BeanFactoryReference beanFactoryReference;

	public AbstractServiceBean() {
		init();
	}

	public AbstractServiceBean(String _configLocation) {
		this.setConfigLocation(_configLocation);
		init();
	}

	public void init() {
		Assertion.notNull(configLocation, "The argument [configLocation] not found");
		Assertion.notNull(beanFactoryLocatorKey, "The argument [beanFactoryLocatorKey] not found");

		BeanFactoryLocator beanFactoryLocator = ContextSingletonBeanFactoryLocator.getInstance(configLocation);
		BeanFactoryReference beanFactoryReferenceTarget = beanFactoryLocator.useBeanFactory(beanFactoryLocatorKey);

		ProxyFactory pf = new ProxyFactory();
		pf.addInterface(BeanFactoryReference.class);
		pf.setTarget(beanFactoryReferenceTarget);
		this.beanFactoryReference = (BeanFactoryReference) pf.getProxy();

		if (this.beanFactoryReference.getFactory() instanceof ConfigurableApplicationContext) {
			AutowireCapableBeanFactory autowireCapableBeanFactory = ((ConfigurableApplicationContext) this.beanFactoryReference.getFactory()).getBeanFactory();
			autowireCapableBeanFactory.autowireBeanProperties(this, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, false);
		}

		WeakReferenceMonitor.monitor(this.beanFactoryReference, new BeanFactoryReferenceReleaseListener(beanFactoryReferenceTarget));
	}

	public void init(String _configLocation) {
		this.setConfigLocation(_configLocation);
		init();
	}

	protected BeanFactory getBeanFactory() {
		if (null != this.beanFactoryReference) {
			return this.beanFactoryReference.getFactory();
		}
		return null;
	}

	protected void release() {
		beanFactoryReference.release();
		beanFactoryReference = null;
	}

	/**
	 * Implementation of WeakReferenceMonitor's ReleaseListener callback interface. Release the given
	 * BeanFactoryReference if the monitor detects that there are no strong references to the handle anymore.
	 */
	private static class BeanFactoryReferenceReleaseListener implements WeakReferenceMonitor.ReleaseListener {

		private final BeanFactoryReference beanFactoryReference;

		public BeanFactoryReferenceReleaseListener(BeanFactoryReference beanFactoryReference) {
			this.beanFactoryReference = beanFactoryReference;
		}

		public void released() {
			this.beanFactoryReference.release();
		}
	}

	/**
	 * @return Returns the beanFactoryLocatorKey.
	 */
	public String getBeanFactoryLocatorKey() {
		return beanFactoryLocatorKey;
	}

	/**
	 * @param beanFactoryLocatorKey
	 *            The beanFactoryLocatorKey to set.
	 */
	public void setBeanFactoryLocatorKey(String beanFactoryLocatorKey) {
		this.beanFactoryLocatorKey = beanFactoryLocatorKey;
	}

	/**
	 * @return Returns the configLocation.
	 */
	public String getConfigLocation() {
		return configLocation;
	}

	/**
	 * @param configLocation
	 *            The configLocation to set.
	 */
	public void setConfigLocation(String configLocation) {
		this.configLocation = configLocation;
	}

}
